import java.awt.*;
import java.awt.geom.*;
import java.util.*;
import java.awt.image.BufferedImage;

/**
* Beispiel fuer die Verwendung von Doube Buffering.
* Es wird das Beispiel der Uhr aus der Klasse NonSynchronizedClockExample
* mittels Double-Buffering implementiert.
*
* @author Frank Klawonn
* Letzte Aenderung 27.05.2005
*
* @see NonSynchronizedClockExample
* @see BufferedImageDrawer
*/
public class DoubleBufferingClockExample extends TimerTask
{
  //Das Fenster, in dem alles angezeigt wird.
  private BufferedImageDrawer buffid;

  //Der Hintergrund fuer alle Bilder
  private BufferedImage bg;

  //Seitenlaenge fuer die Uhr fuer den Rahmen der Uhr
  private double frameSize;

  //Laenge des Sekundenzeigers
  private double handLength;

  //Breite des Sekundenzeigers
  private double handWidth;

  //Rahmen der Uhr zentriert im Koordinatenursprung
  private Rectangle2D.Double clockFrame;

  //Sekundenzeiger, dessen unteres Ende zu Beginn auf dem Koordinatenursprung
  //steht.
  private Rectangle2D.Double clockHand;

  //Diese Rotation gibt an, um welchen Winkel der Sekundenzeiger
  //in jedem Schritt weiterbewegt.
  private AffineTransform singleRotation;

  //In dieser Rotation werden die einzelnen Rotation akkumuliert.
  private AffineTransform accumulatedRotation;

  //Diese Translation gibt an, wie weit die Uhr in jedem Schritt
  //weiterbewegt werden soll.
  private AffineTransform singleTranslation;

  //In dieser Translation werden die einzelnen Translationen akkumuliert.
  private AffineTransform accumulatedTranslation;

  //Diese Transformation soll die beiden Einzelbewegungen des
  //Sekundenzeigers (Rotation und Bewegung der Uhr) zusammenfassen.
  private AffineTransform handTransform;


  /**
  * Konstruktor
  *
  * @param bid          buffid
  * @param backGround   bg
  * @param height       Hoehe des Fensters
  * @param delay        Gibt an, nach wie viel Millisekunden das Bild neu
  *                     gezeichnet werden soll (wird zur Synchronisation der
  *                     Uhr benoetigt)
  */
  DoubleBufferingClockExample(BufferedImageDrawer bid, BufferedImage backGround,
                              int height,int delay)
  {
    buffid = bid;

    //Die Linien sollen eine Dicke von 3.0 statt 1.0 haben.
    buffid.g2dbi.setStroke(new BasicStroke(3.0f));

    //Verwendung von Antialiasing, um die Raender weniger ausgefranst
    //erscheinen zu lassen
    buffid.g2dbi.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                                  RenderingHints.VALUE_ANTIALIAS_ON);

    /*In yUp wird die Translation definiert, mit der Objekte in "realen"
      Koordinaten, bei denen die y-Achse nach oben zeigt, so in das
      Fenster gezeichnet werden, dass der Koordinatenursprung in der linken
      unteren Ecke im Fenster befindet.
     */
    AffineTransform yUp = new AffineTransform();
    yUp.setToScale(1, -1);
    AffineTransform translate = new AffineTransform();
    translate.setToTranslation(0, height);
    yUp.preConcatenate(translate);

    //Wende die Transformation auf das Graphics2D-Objekt an. Damit wird
    //alles in realen Koordinaten gezeichnet.
    buffid.g2dbi.transform(yUp);

    buffid.g2dbi.setPaint(Color.black);

    bg = backGround;

    //Festlegung der einzelnen Parameter zum Zeichnen

    frameSize = 100;
    handLength = 40;
    handWidth = 5;

    clockFrame = new Rectangle2D.Double(-frameSize/2,-frameSize/2,
                                        frameSize,frameSize);

    clockHand = new Rectangle2D.Double(-handWidth/2,0,
                                       handWidth,handLength);


    singleRotation = new AffineTransform();
    singleRotation.setToRotation(-delay*Math.PI/30000);

    accumulatedRotation = new AffineTransform();

    singleTranslation = new AffineTransform();
    singleTranslation.setToTranslation(2,1);

    accumulatedTranslation = new AffineTransform();
    //Damit sich die Uhr zu Beginn im Inneren des Fensters befindet,
    //soll die Translation schon am Anfang eine Verschiebung nach
    //rechts und nach oben ausueben.
    accumulatedTranslation.setToTranslation(150,150);

    handTransform = new AffineTransform();
  }


  //Diese Methode wird in regelmaessigen Abstaenden aufgerufen. In ihr werden
  //jeweils das aktualisierte Bild berechnet und die Methode repaint zum
  //Zeichnen im Bildschirmfenster aufgerufen.
  public void run()
  {
     //Gesamttransformation des Sekundenzeigers: Erst Rotation, dann
     //Verschiebung
     handTransform.setTransform(accumulatedRotation);
     handTransform.preConcatenate(accumulatedTranslation);

     //Zeichnen des Hintergrunds
     buffid.g2dbi.drawImage(bg,0,0,null);

     //Zeichnen des Rahmens der Uhr
     buffid.g2dbi.draw(accumulatedTranslation.createTransformedShape(clockFrame));

     //Zeichnen des Sekundenzeigers
     buffid.g2dbi.fill(handTransform.createTransformedShape(clockHand));

     //Mit diesem Aufruf wird das Bild im Fenster aktualisiert.
     buffid.repaint();


     //Berechnung der naechsten Gesamtverschiebung der Uhr
     accumulatedTranslation.preConcatenate(singleTranslation);

     //Berechnung der naechsten Gesamtrotation des Zeigers
     accumulatedRotation.preConcatenate(singleRotation);


  }



  public static void main(String[] argv)
  {

    //Breite des Fensters
    int width = 780;
    //Hoehe des Fensters
    int height = 530;

    //Gibt an (in Millisekunden), nach wie langer Zeit das Bild aktualisiert
    //werden soll.
    int delay = 50;

    //Das BufferedImage, das im Fenster jeweils gezeichnet wird.
    BufferedImage bi = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);

    //Der Hintergrund
    BufferedImage backGround = new BufferedImage(width,height,BufferedImage.TYPE_INT_RGB);
    Graphics2D g2dBackGround = backGround.createGraphics();

    //Die Linien sollen eine Dicke von 3.0 statt 1.0 haben.
    g2dBackGround.setStroke(new BasicStroke(3.0f));

    //Der Hintergrund wird zunaechst weiss gemalt.
    g2dBackGround.setPaint(Color.white);
    g2dBackGround.fill(new Rectangle(0,0,width,height));


    //Dieses Hintergrundrechteck rahmt das Gesamtbild ein.
    Rectangle2D.Double windowFrame = new Rectangle2D.Double(50,50,
                                                            width-100,
                                                            height-100);
    g2dBackGround.setPaint(Color.black);
    g2dBackGround.draw(windowFrame);

    //Das Fenster, in dem alles angezeigt wird.
    BufferedImageDrawer bid = new BufferedImageDrawer(bi,width,height);

    //Die TimerTask, in der die Berechnungen zum Neuzeichnen stattfinden.
    DoubleBufferingClockExample dbce = new DoubleBufferingClockExample(bid,
                                                                       backGround,
                                                                       height,
                                                                       delay);

    Timer t = new Timer();
    t.scheduleAtFixedRate(dbce,0,delay);
    
  }


}


